module Datapath( iClk, iReset, iPCWriteCond, iPCWrite, iIorD, iMemRead, iMemWrite, iIRWrite, iRegDst, iMemtoReg, iRegWrite, iALUSrcA, iALUOp, iALUSrcB, iPCSource, oInstruction
, iMemInstructionWriteMode, iMemAddress, iMemWriteData
, oInst0, oData1, oData2, oData5, oData16, oData17 //added for test
);

input iClk, iReset, iPCWriteCond, iPCWrite, iIorD, iMemRead, iMemWrite, iIRWrite, iRegDst, iMemtoReg, iRegWrite, iALUSrcA;
input [2:0] iALUOp;
input [1:0] iALUSrcB, iPCSource;

//Added to write instructions to memory
input iMemInstructionWriteMode;
input [31:0] iMemAddress, iMemWriteData;



output [31:0] oInstruction;

output [31:0] oInst0, oData1, oData2, oData5, oData16, oData17; //for test

wire [31:0] PC, NextPC, MemAddress, MemData_RegIn, MemData, Instruction, WriteData, RegData1_RegIn, RegData2_RegIn, RegData1, RegData2, ExtendedImm, ALUXin, ALUYin, ALUOut_RegIn, ALUOut;
wire [4:0] WriteRegister;
wire Zero, i0, i1;


wire [64:0] MemInput;


assign oInstruction=Instruction;

and a0 (i0,iPCWriteCond,Zero);
or  b0 (i1,iPCWrite,i0);

//aligned as lecture note p94
PC_Register U_PC(.iPC(NextPC), .oPC(PC), .iPCWrite(i1), .iReset(iReset), .iClk(iClk)); 

MUX_2to1 #(32) U_MemoryAddressSelector(.D0({2'b0, PC[31:2]}), .D1(ALUOut), .S0(iIorD), .Y(MemAddress)); 


//Added to write instructions to memory. if iMemInstructionWriteMode==1, U_Memory ignores datapath signals and get instructions from the outside; otherwise it operates normally.
MUX_2to1 #(65) U_WriteInstruction(.D0({iMemWrite, MemAddress, RegData2}), .D1({1'b1, iMemAddress, iMemWriteData}), .S0(iMemInstructionWriteMode), .Y(MemInput)); 


Memory U_Memory(.iClk(iClk), .iMemRead(iMemRead), .iMemWrite(MemInput[64]), .iAddress(MemInput[63:32]), .iWriteData(MemInput[31:0]), .oMemData(MemData_RegIn)
, .oInst0(oInst0), .oData1(oData1), .oData2(oData2), .oData5(oData5), .oData16(oData16), .oData17(oData17) //for test
);

Instruction_Register U_Instruction_Register(.iInstructionIn(MemData_RegIn), .oInstructionOut(Instruction), .iIRWrite(iIRWrite), .iReset(iReset), .iClk(iClk));

MemoryData_Register U_MemoryData_Register(.iMDReg(MemData_RegIn), .oMDReg(MemData), .iReset(iReset), .iClk(iClk)); 

MUX_2to1 #(5) U_WriteRegisterSelector(.D0(Instruction[20:16]), .D1(Instruction[15:11]), .S0(iRegDst), .Y(WriteRegister)); 

MUX_2to1 #(32) U_WriteDataSelector(.D0(ALUOut), .D1(MemData), .S0(iMemtoReg), .Y(WriteData)); 

RF U_RF(.iClk(iClk), .iRegWrite(iRegWrite), .iReset(iReset), .iReadReg1(Instruction[25:21]), .iReadReg2(Instruction[20:16]), .iWriteReg(WriteRegister), .iWriteData(WriteData), .oReadData1(RegData1_RegIn), .oReadData2(RegData2_RegIn)); 

AReg U_AReg(.iClk(iClk), .iReset(iReset), .iAReg(RegData1_RegIn), .oAReg(RegData1)) ;
BReg U_BReg(.iClk(iClk), .iReset(iReset), .iBReg(RegData2_RegIn), .oBReg(RegData2)) ;

SignExtend16to32 U_SignExtend16to32(.I(Instruction[15:0]), .O(ExtendedImm));

MUX_2to1 #(32) ALUXinSelector(.D0(PC), .D1(RegData1), .S0(iALUSrcA), .Y(ALUXin)); 

MUX_4to1 #(32) ALUYinSelector(.D3(ExtendedImm<<2), .D2(ExtendedImm), .D1(32'd4), .D0(RegData2), .S(iALUSrcB), .Y(ALUYin)); 

ALU U_ALU(.z(ALUOut_RegIn), .alu(iALUOp), .x(ALUXin), .y(ALUYin), .zero(Zero));

alu_out U_alu_out(.Q(ALUOut), .D(ALUOut_RegIn), .CLK(iClk), .RESET(iReset));


MUX_4to1 #(32) U_NextPCSelector(.D3(32'b0), .D2({PC[31:28], Instruction[25:0], 2'b0}), .D1(ALUOut), .D0(ALUOut_RegIn), .S(iPCSource), .Y(NextPC));

endmodule
